package com.yifeng.repo.micro.service.server.engine.core;

import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.gomcarter.frameworks.base.common.AssertUtils;
import com.gomcarter.frameworks.base.common.RequestUtils;
import com.google.common.base.Strings;
import com.yifeng.repo.micro.service.server.engine.manager.RequestBodyManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.gomcarter.frameworks.base.common.CustomLocalDateTimeUtils.*;

/**
 * Created by daibing on 2023/3/15.
 */
@Slf4j
public class JsonHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    private static final ObjectMapper objectMapper = JsonMapper.builder()
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES)
            .enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
            .enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
            .build();

    static {
        // 仅需要添加反向序列化的设置
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TIME_FORMATTER));
        objectMapper.registerModule(timeModule);
    }

    private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
    private final List<HandlerMethodArgumentResolver> argumentResolvers;

    public JsonHandlerMethodArgumentResolver(List<HandlerMethodArgumentResolver> argumentResolvers) {
        this.argumentResolvers = argumentResolvers;
    }

    /**
     * 参数明确指定@RequestBody之外的都使用此解析器
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return !(parameter.hasMethodAnnotation(GetMapping.class)
                || parameter.hasMethodAnnotation(PutMapping.class)
                || parameter.hasMethodAnnotation(DeleteMapping.class)
                || parameter.hasParameterAnnotation(RequestBody.class)
                || parameter.hasParameterAnnotation(RequestParam.class));
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
                                  NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        // 1. 检查请求信息
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        AssertUtils.notNull(request, "请求信息错误！");
        if (request.getDispatcherType() == DispatcherType.ERROR || request.getDispatcherType() == DispatcherType.FORWARD) {
            return request;
        }

        // 2. 检查请求类型，ContentType未指定或者application/json
        String contentType = request.getContentType();
        if (Strings.isNullOrEmpty(contentType) || contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
            // 从线程变量获取body Json 对象
            JsonRequestWrapper requestWrapper = nativeWebRequest.getNativeRequest(JsonRequestWrapper.class);
            AssertUtils.notNull(requestWrapper, "请求包装器信息错误！");
            if (RequestBodyManager.get() == null) {
                JsonNode jsonNode = objectMapper.readValue(RequestUtils.body(request.getReader()), JsonNode.class);
                RequestBodyManager.set(jsonNode);
            }
            // 转换参数Json对象到具体业务对象类型
            String parameterName = methodParameter.getParameterName();
            Class<?> parameterType = methodParameter.getParameterType();
            Type dataType = methodParameter.getGenericParameterType();
            JsonNode subNode = RequestBodyManager.get().get(parameterName);
            return convertValue(parameterName, parameterType, dataType, subNode);
        }

        // 3. 检查请求类型，如果不符合就继续原本的处理策略
        HandlerMethodArgumentResolver resolver = getArgumentResolver(methodParameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" + methodParameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        return resolver.resolveArgument(methodParameter, modelAndViewContainer, nativeWebRequest, webDataBinderFactory);
    }

    /**
     * 根据类型转换
     */
    private Object convertValue(String parameterName, Class<?> parameterType, Type firstDataType, JsonNode json) {
        // 第一层参数不是泛型，类似 String
        if (!(firstDataType instanceof ParameterizedType)) {
            return objectMapper.convertValue(json, parameterType);
        }
        // 第一层参数是泛型，类似 List<SysMenuParamExt> paramsList
        Class<?> firstParametrized = (Class<?>) ((ParameterizedType) firstDataType).getRawType();
        // 第一层泛型参数是Map类型做特殊处理，类似 Map<String, Object> map
        if (firstParametrized == Map.class) {
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(Map.class, Object.class, Object.class);
            return objectMapper.convertValue(json, javaType);
        }

        // 第二层参数
        Type secondDataType = ((ParameterizedType) firstDataType).getActualTypeArguments()[0];
        // 第二层参数不是泛型，类似 List<SysMenuParamExt> paramsList
        if (!(secondDataType instanceof ParameterizedType)) {
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(parameterType, (Class<?>) secondDataType);
            return objectMapper.convertValue(json, javaType);
        }
        // 第二层参数是泛型，类似 List<Map<String, Object>> mapsList
        Class<?> secondParametrized = (Class<?>) ((ParameterizedType) secondDataType).getRawType();
        // 第二层泛型参数是Map类型做特殊处理，类似 List<Map<String, Object>> mapsList
        if (secondParametrized == Map.class) {
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(parameterType, Map.class);
            return objectMapper.convertValue(json, javaType);
        }

        // 第三层参数
        Type thirdDataType = ((ParameterizedType) secondDataType).getActualTypeArguments()[0];
        // 第三层参数不是泛型，类似 List<Set<String>> dataSet
        if (!(thirdDataType instanceof ParameterizedType)) {
            JavaType innerJavaType = objectMapper.getTypeFactory().constructParametricType(secondParametrized, (Class<?>) thirdDataType);
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(parameterType, innerJavaType);
            return objectMapper.convertValue(json, javaType);
        }
        // 第三层参数是泛型直接抛异常
        throw new IllegalArgumentException("不支持三层及以上泛型：parameterName=" + parameterName + ", firstDataType=" + firstDataType);
    }

    /**
     * copy from HandlerMethodArgumentResolverComposite#getArgumentResolver
     */
    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }
}
